#include "pch.h"
#include "Shader.h"
#include "GLUtils.h"

ImplementEnumToStrBegin(ShaderType)
EnumToStrItem(ShaderType::eVertex)
EnumToStrItem(ShaderType::eFagment)
ImplementEnumToStrEnd;


ShaderType ShaderCompile::GetType(GLuint shaderHandle)
{
	GLint type;
	glGetShaderiv(shaderHandle, GL_SHADER_TYPE, &type);
	
	return static_cast<ShaderType>(type);
}

bool ShaderCompile::IsDeleted(GLuint shaderHandle)
{
	GLint value;
	glGetShaderiv(shaderHandle, GL_DELETE_STATUS, &value);
	return value == GL_TRUE;
}

bool ShaderCompile::IsCompiled(GLuint shaderHandle)
{
	GLint value;
	glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &value);
	return value == GL_TRUE;
}

int ShaderCompile::GetLogLength(GLuint shaderHandle)
{
	GLint value;
	glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &value);
	return value;
}

bool ShaderCompile::GetLogInfo(GLuint shaderHandle, std::string& logInfo)
{
	GLint length = GetLogLength(shaderHandle);
	if (length > 0)
	{
		logInfo.assign(length, 0);
		glGetShaderInfoLog(shaderHandle, length, NULL, &*logInfo.begin());
		return true;
	}
	return false;
}

int ShaderCompile::GetSourceLength(GLuint shaderHandle)
{
	GLint value;
	glGetShaderiv(shaderHandle, GL_SHADER_SOURCE_LENGTH, &value);
	return value;
}

GLuint Shader::CreateProgram(ShaderData data)
{
	ShaderCompile cShader(data);

	cShader.Compile();

	ShaderProgram lShader(cShader.ID());

	lShader.Link();

	return lShader.ProgramHandle();
}

GLuint Shader::CreateProgram(ShaderDatas datas)
{
	std::vector<GLuint> vecShader;
	for (unsigned int i = 0; i < datas.size(); ++i)
	{
		ShaderCompile cShader(datas[i]);
		cShader.Compile();

		vecShader.push_back(cShader.ID());
	}

	ShaderProgram lShader(vecShader);

	lShader.Link();

	return lShader.ProgramHandle();
}

///////////////////////////////////////////////////////////////////////////

ShaderCompile::ShaderCompile(const ShaderData& sData, bool hasLog/* = true*/)
	:m_shaderData(sData), m_hasLog(hasLog), m_uID(0)
{
	Init();
}

void ShaderCompile::Init()
{
	if (m_shaderData.m_type == ShaderType::eVertex)
	{
		if (m_uID == 0)
		{
			m_uID = glCreateShader(GL_VERTEX_SHADER);
		}
	}
	else if (m_shaderData.m_type == ShaderType::eFagment)
	{
		if (m_uID == 0)
		{
			m_uID = glCreateShader(GL_FRAGMENT_SHADER);
		}
	}
}

bool ShaderCompile::Compile()
{
	const char* source = m_shaderData.m_source.c_str();
	int length = (int)m_shaderData.m_source.size();
	glShaderSource(m_uID, 1, &source, &length);
	glCompileShader(m_uID);

	if (!tgl::GLUtils::CheckOpenGLError())
	{
		return false;
	}

	if (m_hasLog)
	{
		std::string log;
		GetLogInfo(m_uID, log);
		//log = CharConvert::utf8_to_gbk(log);
		log = StringUtils::Format("Compile %s -Handle:%d LOG:%s", ShaderTypeToStr[m_shaderData.m_type], m_uID, log);
		LOG->info(log);
	}

	bool secceed = IsCompiled(m_uID);
	return secceed;
}

GLuint ShaderCompile::ID() const
{
	return m_uID;
}

ShaderProgram::ShaderProgram(GLuint shader, bool hasLog/* = true*/)
	:m_hasLog(hasLog)
{
	m_vecShader.push_back(shader);
}

ShaderProgram::ShaderProgram(const std::vector<GLuint>& vecShader, bool hasLog/* = true*/)
	:m_vecShader(vecShader), m_hasLog(hasLog)
{

}

bool ShaderProgram::Link()
{
	m_program = glCreateProgram();

	for (unsigned int i = 0; i < m_vecShader.size(); ++i)
	{
		glAttachShader(m_program, m_vecShader[i]);
	}

	glLinkProgram(m_program);

	if (m_hasLog)
	{
		std::string log;
		GetLogInfo(m_program, log);
		log = CharConvert::utf8_to_gbk(log);
		LOG->info(StringUtils::Format("Link Program-Handle:%d Log:%s", m_program, log));
	}
	
	bool secceed = IsLinked(m_program);
	return secceed;
}

GLuint ShaderProgram::ProgramHandle() const
{
	return m_program;
}

bool ShaderProgram::IsDeleted(GLuint programHandle)
{
	GLint value = 0;
	glGetProgramiv(programHandle, GL_DELETE_STATUS, &value);

	return value == GL_TRUE;
}

bool ShaderProgram::IsLinked(GLuint programHandle)
{
	GLint value = 0;
	glGetProgramiv(programHandle, GL_LINK_STATUS, &value);

	return value == GL_TRUE;
}

int ShaderProgram::GetLogLength(GLuint programHandle)
{
	GLint value = 0;
	glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH, &value);

	return value;

}

int ShaderProgram::GetLogInfo(GLuint programHandle, std::string& logInfo)
{
	GLint length = GetLogLength(programHandle);
	if (length > 0)
	{
		logInfo.assign(length, 0);
		glGetProgramInfoLog(programHandle, length, NULL, &*logInfo.begin());
		return true;
	}
	return false;
}

int ShaderProgram::GetAttechedShaderSize(GLuint programHandle)
{
	GLint value = 0;
	glGetProgramiv(programHandle, GL_ATTACHED_SHADERS, &value);

	return value;
}

int ShaderProgram::GetActiveAttirbuteMaxLength(GLuint programHandle)
{
	GLint value = 0;
	glGetProgramiv(programHandle, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &value);

	return value;
}

int ShaderProgram::GetActiveAttirbuteSize(GLuint programHandle)
{
	GLint value = 0;
	glGetProgramiv(programHandle, GL_ACTIVE_ATTRIBUTES, &value);

	return value;
}

int ShaderProgram::GetAcviteUniformSize(GLuint programHandle)
{
	GLint value = 0;
	glGetProgramiv(programHandle, GL_ACTIVE_UNIFORMS, &value);

	return value;
}

int ShaderProgram::GetAcviteUniformMaxLength(GLuint programHandle)
{
	GLint value = 0;
	glGetProgramiv(programHandle, GL_ACTIVE_UNIFORM_MAX_LENGTH, &value);

	return value;
}

ShaderData::ShaderData()
{

}

ShaderData::ShaderData(const std::string& source, ShaderType type)
	:m_source(source), m_type(type)
{

}

ShaderData::ShaderData(std::string&& source, ShaderType type)
	:m_source(std::move(source)), m_type(type)
{

}

ShaderData::ShaderData(ShaderType type)
	: m_type(type)
{

}
